package com.example.friend.service.user.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.common.core.Utils.ThreadLocalUtil;
import com.example.common.core.constants.CacheConstants;
import com.example.common.core.constants.Constants;
import com.example.common.core.constants.HttpConstants;
import com.example.common.core.domin.LoginUser;
import com.example.common.core.domin.R;
import com.example.common.core.domin.vo.LoginUserVO;
import com.example.common.core.enums.ResultCode;
import com.example.common.core.enums.UserIdentity;
import com.example.common.core.enums.UserStatus;
import com.example.common.redis.service.RedisService;
import com.example.common.security.exception.ServiceException;
import com.example.common.security.service.TokenService;
import com.example.friend.domain.user.User;
import com.example.friend.domain.user.dto.UserDTO;
import com.example.friend.domain.user.dto.UserUpdateDTO;
import com.example.friend.domain.user.vo.UserVO;
import com.example.friend.manager.UserCacheManager;
import com.example.friend.mapper.user.UserMapper;
import com.example.friend.service.user.IUserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private UserCacheManager userCacheManager;;

    @Value("${sms.code-expiration:5}")
    private Long phoneCodeExpiration; //code过期时间

    @Value("${sms.send-limit:3}")
    private Integer sendLimit;//申请验证码次数

    @Value("${jwt.secret}")
    private String secret;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;

    @Override
    public boolean sendCode(UserDTO userDTO) {
        if(!checkPhone(userDTO.getPhone())){
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }
        //验证码生成一分钟内不允许用户再去获取验证码
        //获取该手机号code的剩余时间
        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());
        Long expire = redisService.getExpire(phoneCodeKey,TimeUnit.SECONDS);
        //计算过去时间
        if (expire != null && (phoneCodeExpiration * 60 - expire) < 60){
            throw new ServiceException(ResultCode.FAILED_FREQUENT);
        }
        //操作次数限制 -- 每天的验证码获取次数有一个限制 50次 第二天计数器清0,重新开始计数
        //怎么存,存哪 --> 操作这个次数数据频繁,不需要长期存储,记录的次数有有效时间的(当天有效) --->redis
        //设计缓存结构 String key: c:t:手机号
        //获取已经请求的次数和50进行对比  如果大于等于50抛出异常,如果不大于限制执行后续逻辑,并且将获取计数+1
        String codeTimeKey = getCodeTimeKey(userDTO.getPhone());
        Long sendTimes = redisService.getCacheObject(codeTimeKey,Long.class);
        if (sendTimes != null && sendTimes >= sendLimit){
            throw new ServiceException(ResultCode.FAILED_TIME_LIMIT);
        }
        //生成验证码
        String code = RandomUtil.randomNumbers(6);
        //模拟发送验证码
        //todo
        //生成code存储到Redis中 -- 数据结构:String key: p:c:手机号 value:code
        redisService.setCacheObject(phoneCodeKey,code,phoneCodeExpiration, TimeUnit.MINUTES);
        //请求次数计数 +1
        redisService.increment(codeTimeKey);//如果key不存在redis会自动创建的
        if (sendTimes == null){
            //说明是当天第一次发起获取验证码的请求 -- 设置phoneCodeKey的过期时间(到第二天的00:00:00)
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeTimeKey, seconds, TimeUnit.SECONDS);
        }
        return true;
    }

    //用户登录
    @Override
    public String codeLogin(String code, String phone) {
        //先完成验证码的比对(新老用户都需要去执行)
        checkCode(phone,code);

        //判断该手机号的用户是新用户还是老用户
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getPhone, phone));
        System.out.println("查询到的user信息为 : " + user.toString());
        if (user == null){
            //新用户先执行注册逻辑
            user = new User();
            user.setPhone(phone);
            user.setStatus(UserStatus.Normal.getValue());
            userMapper.insert(user);
        }
        //老用户 -- 直接执行登录逻辑
        //生成token -- 用户身份要转为ORDINARY(普通用户)
        return tokenService.createToken(user.getUserId(),secret, UserIdentity.ORDINARY.getValue(),user.getNickName(),user.getHeadImage());
    }

    //登出
    @Override
    public boolean logout(String token) {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return tokenService.deleteLoginUser(token,secret);
    }

    //获取登录用户信息
    @Override
    public R<LoginUserVO> info(String token) {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }

        LoginUser loginUser = tokenService.getLoginUser(token,secret);
        if(loginUser == null){
            return R.fail();
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        if(StrUtil.isNotEmpty(loginUser.getHeadImage())){
            loginUserVO.setHeadImage(downloadUrl + loginUser.getHeadImage());
        }
        return R.ok(loginUserVO);
    }

    @Override
    public UserVO detail() {

        //从ThreadLocal中解析出userId
        Long userId = ThreadLocalUtil.get(Constants.USER_ID,Long.class);
        //尝试从redis中查询用户数据
        if(userId == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        UserVO userVO = userCacheManager.getUserById(userId);
        if(userVO == null){
            //getUserById在查询的时候会刷新缓存,如果刷新缓存还是没有,就是错误userId了
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if(StrUtil.isNotEmpty(userVO.getHeadImage())){
            userVO.setHeadImage(downloadUrl + userVO.getHeadImage());
        }
        return userVO;
    }

    //编辑用户信息
    @Override
    public int edit(UserUpdateDTO userUpdateDTO) {
        //获取User(Thread中)
        User user = getThreadLoginUser();
        user.setNickName(userUpdateDTO.getNickName());
        user.setSex(userUpdateDTO.getSex());
        user.setSchoolName(userUpdateDTO.getSchoolName());
        user.setMajorName(userUpdateDTO.getMajorName());
        user.setPhone(userUpdateDTO.getPhone());
        user.setEmail(userUpdateDTO.getEmail());
        user.setWechat(userUpdateDTO.getWechat());
        user.setIntroduce(userUpdateDTO.getIntroduce());
        //更新存储器
        return editStorage(user);
    }

    //更新用户头像信息
    @Override
    public int updateHeadImage(String headImage) {
        User user = getThreadLoginUser();
        user.setHeadImage(headImage);
        return editStorage(user);
    }

    //从ThreadLocal中获取用户id,并查询user放回
    private User getThreadLoginUser() {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID,Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        return user;
    }

    //更新Mysql和redis中存储的用户数据
    private int editStorage(User user) {
        //更新用户详情中的数据
        userCacheManager.refreshUser(user);
        //更新token中的信息
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(Constants.USER_KEY,String.class));
        //更新数据库中数据
        return userMapper.updateById(user);
    }

    private void checkCode(String phone, String code) {
        //获取redis中的验证码
        String phoneCodeKey = getPhoneCodeKey(phone);
        String cacheCode = redisService.getCacheObject(phoneCodeKey,String.class);
        if (StringUtils.isEmpty(cacheCode)){
            throw new ServiceException(ResultCode.FAILED_INVALID_CODE);
        }
        if (!code.equals(cacheCode)){
            System.out.println("[codeLogin] 获取到的code:" +code  + "和redis:" + cacheCode);
            //验证码错误
            throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
        }
        //验证码比对成功 -- 删除redis中的验证码
        redisService.deleteObject(phoneCodeKey);
    }

    public static boolean checkPhone(String phone) {
        //验证手机号格式
        Pattern regex = Pattern.compile("^1[2|3|4|5|7|8|9][0-9]\\d{8}$");
        Matcher m = regex.matcher(phone);
        return m.matches();
    }

    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    private String getCodeTimeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY + phone;
    }

}
